package robocup.component.statistics;

import robocup.component.Synchro;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.ControllerNumbers.VIEW_ANGLE;
import robocup.component.infotypes.InfoReceiver;
import robocup.component.infotypes.PlayModeInfo;
import robocup.component.infotypes.PlayerSayInfo;
import robocup.component.infotypes.PlayerTypesInfo;
import robocup.component.infotypes.RefereeInfo;
import robocup.component.infotypes.SenseBodyInfo;
import robocup.component.infotypes.VisualInfo;
import robocup.component.math.RCMath;

/**
* Info statistics.
* 
* This statistics show the following:
* <ul>
*   <li>Number of VIs Number of late VIs (VIs that arrived after the sending)</li>
*   <li>Ratio of late VIs</li>
*   <li>Number of 2nd VIs (VIs that arrive in a cycle where already arrived a VI)</li>
*   <li>Ratio of 2nd VIs</li>
*   <li>Average arrival time offset of VIs given the estimated arrival quarter</li>
*   <li>Variance of the arrival time offset of VIs</li>
*   <li>Ratio of VIs with view mode = narrow</li>
*   <li>Ratio of VIs with view mode = medium</li>
*   <li>Ratio of actions sent based on a BS-info</li>
*   <li>Number of Cycles without a VI arrival</li>
*   <li>Ratio of Cycles without a VI arrival</li>
*   <li>Average length of a cycle</li>
*   <li>Variance of the cylce length</li>
*   <li>Average time needed for calculating an action</li>
*   <li>Variance of the time needed for calculating an action</li>
* </ul>
* 
* @author Robert Wetzker
*/
public class InfoStatistic implements InfoReceiver{

   // number VIs
   private int     viCounter           = 0;

   // number BS
   private int     sbCounter           = 0;

   // accumulated time between BS
   private long    accumSBTime         = 0;

   // variance of sb_length
   private double  varSBLength         = 0;
   
   //the arrival offset of VI in the estimated arrival quarter
   private long accumViOffset = 0;
   
   //the variance of the VI arrival offset
   private double varViOffset = 0;

   // VIs that arrived after sending
   private int     lateVIs             = 0;

   // VIs that are not the first VI of the cycle
   private int     secondVIs           = 0;

   // cycles without VIs
   private int     emptyCycles         = 0;

   // cycles with viewmode = 75ms
   private int     vi75                = 0;

   // cycles with viewmode = 150ms
   private int     vi150               = 0;

   // time between vi_arrival and sending
   private long    actionEffort        = 0;

   // time between vi_arrival and sending
   private long    actionCounter = 0;

   // var of action effort
   private double  varActionEffort     = 0;

   // number of actions sent based on a VI
   private int     sentAfterVI         = 0;

   // number of actions sent based on a BS
   private int     sentAfterBS         = 0;
   
   // the synchro bean
   private Synchro synchro;

  private SenseBodyInfo lastBs;

  private long actionWithBallEffort;

  private int actionWithBallCounter;

  private double varActionWithBallEffort;


   /**
    * The Constructor.
    * 
    * @param synchro -
    *          the synchro bean
    */
   public InfoStatistic(
       final Synchro synchro) {

     this.synchro = synchro;
   }

   /**
    * update statistic after an VI arrival
    * 
    * @param info -
    *          VI
    */
   public void update(final VisualInfo info) {
     
     // as the Server sends sometimes strange things during the
     // init period only Infos that arrive during the game are considered
     if (info.getCycle() == 0) {
       return;
     }
     this.viCounter++;
     if (this.synchro.is2ndVI()) {
       this.secondVIs++;
     }
     else if (this.synchro.wasSent()) {
       this.lateVIs++;
     }
     
     final long offset = (info.getArrivalTime() 
                     - this.synchro.getVIArrivalQuarter() * 25000000
                     - this.lastBs.getArrivalTime()) / 1000000;
     
     this.accumViOffset += offset;
     
     if(this.viCounter > 1){
       this.varViOffset = (double) (this.varViOffset * (this.viCounter - 1) 
                           + Math.pow(offset 
                                 - (double) (this.accumViOffset 
                                     / (this.viCounter - 1)),2)) 
                         / this.viCounter;
     }
   }

   /**
    * update statistic after BS info arrival.
    * 
    * @param info -
    *          BS info
    */
   public void update(final SenseBodyInfo info) {
     
     this.lastBs = info;
     
     // as the Server sends quiet strange things during the
     // init period only Info that arrive during the game are considered
     if (info.getCycle() == 0) {
       return;
     }
     this.sbCounter++;
     if (synchro.getWorld().getLongTermMemory().getVIArrivalTimes()
         .length() > 0 && synchro.getWorld().getLongTermMemory()
         .getBSArrivalTimes().length() > 0) {

       // no VI arrived in last cycle
       if (synchro.getWorld().getLongTermMemory().getLastVIArrivalTime() < synchro
           .getWorld().getLongTermMemory().getLastBSArrivalTime()) {
         this.emptyCycles++;
       }
     }
     if (info.getViewWidth() == VIEW_ANGLE.NARROW) {
       this.vi75++;
     }
     if (info.getViewWidth() == VIEW_ANGLE.NORMAL) {
       this.vi150++;
     }
     if (synchro.getWorld().getLongTermMemory().getBSArrivalTimes()
         .length() > 0 && this.sbCounter > 1) {
       this.accumSBTime += this.synchro.getLastCycleLength();
       this.varSBLength = (double) (this.varSBLength * (this.sbCounter - 1) + Math
           .pow(
               this.synchro.getLastCycleLength() - (double) (this.accumSBTime / (this.sbCounter - 1)),
               2)) / this.sbCounter;
     }
   }

   /**
    * updates the statistics after the sending of an action.
    * 
    * determines the time needed for the action calculation.
    * 
    */
   public void updateAfterSending() {
     if(this.synchro.getWorld().getPlayMode() != PLAY_MODE.PLAY_ON){
       return;
     }
     
     long lastArrivalTime;
     if (synchro.getWorld().getLongTermMemory().getVIArrivalTimes()
         .length() > 0 && synchro.getWorld().getLongTermMemory()
         .getBSArrivalTimes().length() > 0) {
       if (synchro.getWorld().getLongTermMemory().getLastVIArrivalTime() > 
           synchro.getWorld().getLongTermMemory().getLastBSArrivalTime()) {
         lastArrivalTime = synchro.getWorld().getLongTermMemory()
             .getLastVIArrivalTime();
         this.actionEffort += (System.nanoTime() - lastArrivalTime) / 1000000;
         this.actionCounter++;
         this.varActionEffort = (double) (this.varActionEffort * (this.actionCounter - 1) + Math
             .pow(
                 (System.nanoTime() - lastArrivalTime) / 1000000 - (double) (this.actionEffort / this.actionCounter),
                 2)) / this.actionCounter;
         
         if(this.synchro.getWorld().canKick()){
           this.actionWithBallEffort += (System.nanoTime() - lastArrivalTime) / 1000000;
           this.actionWithBallCounter++;
           this.varActionWithBallEffort = (double) (this.varActionWithBallEffort * (this.actionWithBallCounter - 1) + Math
               .pow(
                   (System.nanoTime() - lastArrivalTime) / 1000000 - (double) (this.actionWithBallEffort / this.actionWithBallCounter),
                   2)) / this.actionWithBallCounter;
         }
       }
     }
     if (synchro.getWorld().isViBased()) {
       this.sentAfterVI++;
     }
     else {
       this.sentAfterBS++;
     }
   }

   /**
    * resets the values of the statistics
    */
   public void reset() {

     this.viCounter = 0;
     this.sbCounter = 0;
     this.lateVIs = 0;
     this.secondVIs = 0;
     this.emptyCycles = 0;
     this.accumSBTime = 0;
     this.varSBLength = 0;
     this.actionEffort = 0;
     this.actionCounter = 0;
     this.varActionEffort = 0;
   }

   /**
     * prints the statistic.
     * 
     */
  public void show() {

    System.out.println("RWC"
        + synchro.getWorld().getCycle()
        + " P"
        + synchro.getWorld().getNumber()
        + " VI_Count:"
        + this.viCounter
        + "\t late VIs:"
        + this.lateVIs
        + " %:"
        + RCMath.round2((double) this.lateVIs / this.viCounter * 100)
        + "\t 2nd VIs:"
        + this.secondVIs
        + " %:"
        + RCMath.round2((double) this.secondVIs / this.viCounter * 100)
        + "\t ViOffset:"
        + RCMath.round2(accumViOffset / (this.viCounter - 1))
        + "\t var:"
        + RCMath.round2(varViOffset)
        + "\t 75ms%:"
        + RCMath.round2((double) this.vi75 / this.sbCounter * 100)
        + "\t 150ms%:"
        + RCMath.round2((double) this.vi150 / this.sbCounter * 100)
        + "\t BS%:"
        + RCMath.round2((double) this.sentAfterBS
            / (this.sentAfterBS + this.sentAfterVI)
            * 100)
        + "\t Empty:"
        + this.emptyCycles
        + " %:"
        + RCMath.round2((double) this.emptyCycles / this.sbCounter * 100)
        + "\t avg_cycle_length:"
        + RCMath.round2((double) this.accumSBTime / (this.sbCounter - 1))
        + "\t var:"
        + RCMath.round2(this.varSBLength)
        + "\t time/action:"
        + RCMath.round2((double) this.actionEffort / this.actionCounter)
        + "\t var:"
        + RCMath.round2(this.varActionEffort)
        + "\t time/ballaction:"
        + RCMath.round2((double) this.actionWithBallEffort
            / this.actionWithBallCounter)
        + "\t var_ball:"
        + RCMath.round2(this.varActionWithBallEffort)
        + "\t BallActions: "
        + this.actionWithBallCounter);
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.RefereeInfo)
   */
  public void update(final RefereeInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.PlayModeInfo)
   */
  public void update(final PlayModeInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.PlayerSayInfo)
   */
  public void update(final PlayerSayInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.PlayerTypesInfo)
   */
  public void update(final PlayerTypesInfo info) {

    // TODO Auto-generated method stub
    
  }
}
